//  Copyright (c) 2008  Yoong-ki Ahn & Youngjin Kim < mexif@googlegroups.com>
//  See the enclosed file COPYING for license information (MIT Licence).
//  If you did not receive this file,
//  see http://www.opensource.org/licenses/mit-license.php.

package mexif;

import mexif.TagDB.ExifMetaTagDB_EXIF;

class ExifTime {
	short year;
	short month;
	short day;

	short hour;
	short minute;
	short second;
};

public class ExifUtil {
	final static public int MEXIF_VALUE_BYTE		= 1;
	final static public int MEXIF_VALUE_ASCII		= 2;	///< null terminated.
	final static public int MEXIF_VALUE_UINT16		= 3;    ///< uint16
	final static public int MEXIF_VALUE_UINT32		= 4;	///< uint32
	final static public int MEXIF_VALUE_RATIONAL	= 5;	///< uint32 / uint32
	final static public int MEXIF_VALUE_SINT8		= 6;
	final static public int MEXIF_VALUE_UNDEFINED	= 7;	///< byte array.
	final static public int MEXIF_VALUE_SINT16		= 8;	///< int16
	final static public int MEXIF_VALUE_SINT32		= 9;	///< int32
	final static public int MEXIF_VALUE_SRATIONAL	= 10;	///< int32 / int32
	
	static boolean isExifMarkerPrefixed( final byte[] data, int pos ) {
		if( data.length >= 6 + pos && data[pos+0] == 'E' && data[pos+1] == 'x' &&  data[pos+2] == 'i' &&
				data[pos+3] == 'f' && data[pos+4] == '\0' && data[pos+5] == '\0' )
			return true;
		return false;
	}
	
	static boolean isJfifMarkerPrefixed( final byte[] data, int pos ) {
		if( data.length >= 5 + pos && 
				data[pos+0] == 'J' && data[pos+1] == 'F' && data[pos+2] == 'I' &&
				data[pos+3] == 'F' && data[pos+4] == '\0' )
			return true;
		return false;
	}

	static long uintTagValue(ExifTag tag) {
		return uintTagValueIndex(tag, 0);
	}
	static int intTagValue(ExifTag tag) {
		return intTagValueIndex(tag, 0);
	}
	static double doubleTagValue(ExifTag tag) {
		return doubleTagValueIndex(tag, 0);
	}

	static String stringDoubleTagValueIndex(ExifTag tag, int index) {
		switch( tag.mValueType  )
		{
		case MEXIF_VALUE_UINT16 :
		case MEXIF_VALUE_SINT32 :
		case MEXIF_VALUE_UINT32 :
		case MEXIF_VALUE_BYTE :
		case MEXIF_VALUE_UNDEFINED :
		default :
			return "";

		// @TODO: How to represent unsigned value
		case MEXIF_VALUE_SRATIONAL :
		case MEXIF_VALUE_RATIONAL :
			{
				int a = (int) ByteUtil.get32u( tag.mExif.mData, tag.mValuePointerOffset + 0 + index*8, tag.mIFD.mLittleEndian );
				int b = (int) ByteUtil.get32u( tag.mExif.mData, tag.mValuePointerOffset + 4 + index*8, tag.mIFD.mLittleEndian );
				StringBuffer sb = new StringBuffer();
				sb.append(a);
				sb.append(" / ");
				sb.append(b);
				return sb.toString();
			}
		}
	}
	
	static double doubleTagValueIndex(ExifTag tag, int index) {
		switch( tag.mValueType  )
		{
		case MEXIF_VALUE_UINT16 :
		case MEXIF_VALUE_SINT32 :
		case MEXIF_VALUE_UINT32 :
		case MEXIF_VALUE_BYTE :
		case MEXIF_VALUE_UNDEFINED :
		default :
			return 0;

		// @TODO: How to represent unsigned value
		case MEXIF_VALUE_SRATIONAL :
		case MEXIF_VALUE_RATIONAL :
			{
				int a = (int) ByteUtil.get32u( tag.mExif.mData, tag.mValuePointerOffset + 0 + index*8, tag.mIFD.mLittleEndian );
				int b = (int) ByteUtil.get32u( tag.mExif.mData, tag.mValuePointerOffset + 4 + index*8, tag.mIFD.mLittleEndian );
				return (a) / (double) b;
			}
	
		}
	}

	static long uintTagValueIndex(ExifTag tag, int index ) {
		switch(tag.mValueType) {
		case MEXIF_VALUE_UINT16 :
			return ByteUtil.get16u( tag.mExif.mData, tag.mValuePointerOffset + 2*index, tag.mIFD.mLittleEndian );

		case MEXIF_VALUE_UINT32 :
			return ByteUtil.get32u( tag.mExif.mData, tag.mValuePointerOffset + 4*index, tag.mIFD.mLittleEndian );

		case MEXIF_VALUE_SINT8 :
		case MEXIF_VALUE_SINT16 :
		case MEXIF_VALUE_SINT32 :
		case MEXIF_VALUE_SRATIONAL :
		case MEXIF_VALUE_RATIONAL :
		default :
			return 0;

		case MEXIF_VALUE_BYTE :
		case MEXIF_VALUE_UNDEFINED :
			return (long) ( tag.mExif.mData[ tag.mValuePointerOffset + index ] & 0xFF );
		}
	}
	static int intTagValueIndex(ExifTag tag, int index) {
		switch( tag.mValueType )
		{
		case MEXIF_VALUE_UINT16 :
			return ByteUtil.get16u( tag.mExif.mData, tag.mValuePointerOffset + 2*index, tag.mIFD.mLittleEndian ) & 0xffff;

		case MEXIF_VALUE_SINT32 :
			return (int)ByteUtil.get32u( tag.mExif.mData, tag.mValuePointerOffset + 4*index, tag.mIFD.mLittleEndian );

		case MEXIF_VALUE_SINT8 :
			return tag.mExif.mData[ tag.mValuePointerOffset + index ];
		case MEXIF_VALUE_SINT16 :
			return ByteUtil.get16u( tag.mExif.mData, tag.mValuePointerOffset + 2*index, tag.mIFD.mLittleEndian );

		case MEXIF_VALUE_UINT32 :
		case MEXIF_VALUE_SRATIONAL :
		case MEXIF_VALUE_RATIONAL :
		default :
			return 0;

		case MEXIF_VALUE_BYTE :
		case MEXIF_VALUE_UNDEFINED :
			return ( tag.mExif.mData[ tag.mValuePointerOffset + index ] & 0xFF );
		}
	}

	static boolean isIFD(int sectionFormat) {
		int baseFormat = (((sectionFormat>>24)&0xff) + 1);
		if( EnumSectionFormat.MEXIF_BASE_FORMAT_IFD == baseFormat )
			return true;
		else
			return false;
	}
	
	static boolean isU8Array(int sectionFormat) {
		int baseFormat = (((sectionFormat>>24)&0xff) + 1);
		if( EnumSectionFormat.MEXIF_BASE_FORMAT_U8ARRAY == baseFormat )
			return true;
		else
			return false;
	}
	static boolean isU16Array(int sectionFormat) {
		int baseFormat = (((sectionFormat>>24)&0xff) + 1);
		if( EnumSectionFormat.MEXIF_BASE_FORMAT_U16ARRAY == baseFormat )
			return true;
		else
			return false;
	}
	static boolean isU32Array(int sectionFormat) {
		int baseFormat = (((sectionFormat>>24)&0xff) + 1);
		if( EnumSectionFormat.MEXIF_BASE_FORMAT_U32ARRAY == baseFormat )
			return true;
		else
			return false;
	}
	
	static String getSectionTypeName(int section) {
		switch(section){
		case EnumExifSection.MEXIF_SECTION_0:			return "IFD_0";
		case EnumExifSection.MEXIF_SECTION_1:			return "IFD_1";
		case EnumExifSection.MEXIF_SECTION_EXIF:		return "Exif";
		case EnumExifSection.MEXIF_SECTION_GPS:			return "GPS";
		case EnumExifSection.MEXIF_SECTION_INTEROP:		return "InterOp";
		case EnumExifSection.MEXIF_SECTION_MAKERNOTE:	return "MakerNote";
		default:										return "Unknown";
		}
	}
	
	static String getMakeNamer(int maker){
		switch(maker){
		case EnumMakers.MEXIF_MAKER_AGFA		: return "AGFA";
		case EnumMakers.MEXIF_MAKER_CANON		: return "CANON";
		case EnumMakers.MEXIF_MAKER_CASIO		: return "CASIO";
		case EnumMakers.MEXIF_MAKER_EPSON		: return "EPSON";
		case EnumMakers.MEXIF_MAKER_FUJIFILM	: return "FUJIFILM";
		case EnumMakers.MEXIF_MAKER_KODAK		: return "KODAK";
		case EnumMakers.MEXIF_MAKER_KONICA		: return "KONICA";
		case EnumMakers.MEXIF_MAKER_MINOLTA		: return "MINOLTA";
		case EnumMakers.MEXIF_MAKER_KYOCERA		: return "KYOCERA";
		case EnumMakers.MEXIF_MAKER_NIKON		: return "NIKON";
		case EnumMakers.MEXIF_MAKER_OLYMPUS		: return "OLYMPUS";
		case EnumMakers.MEXIF_MAKER_PANASONIC	: return "PANASONIC";
		case EnumMakers.MEXIF_MAKER_PENTAX		: return "PENTAX";
		case EnumMakers.MEXIF_MAKER_POLAROID	: return "POLAROID";
		case EnumMakers.MEXIF_MAKER_RICOH		: return "RICOH";
		case EnumMakers.MEXIF_MAKER_SAMSUNG		: return "SAMSUNG";
		case EnumMakers.MEXIF_MAKER_SANYO		: return "SANYO";
		case EnumMakers.MEXIF_MAKER_SIGMA		: return "SIGMA";
		case EnumMakers.MEXIF_MAKER_SHARP		: return "SHARP";
		case EnumMakers.MEXIF_MAKER_SONY		: return "SONY";
		case EnumMakers.MEXIF_MAKER_TOSHIBA		: return "TOSHIBA";
		case EnumMakers.MEXIF_MAKER_UNKNOWN:
		default:	return "Unknown Maker";
		}
	}

	static String formatTagName(ExifTag tag) {
		if( tag.mTagDBEntry != null && tag.mTagDBEntry.mName != null )
			return tag.mTagDBEntry.mName;
		else
			return "(Tag 0x" + Integer.toHexString( tag.mTagID ) + ")";
	}
	
	static String formatTagDesc(ExifTag tag) {
		StringBuffer buf = new StringBuffer();
		if( tag.mTagDBEntry != null && tag.mTagDBEntry.mDesc != null )
			buf.append(tag.mTagDBEntry.mDesc);

		buf.append("id:0x");
		buf.append(Integer.toHexString(tag.mTagID));
		buf.append(", ");

		switch(tag.mValueType) {
		case EnumValueType.MEXIF_VALUE_ASCII:
			buf.append("String, length=");
			buf.append(tag.mValueSize);
			return buf.toString();
		case EnumValueType.MEXIF_VALUE_UNDEFINED:
			buf.append("Binary, length=");
			buf.append(tag.mValueSize);
			return buf.toString();
		case EnumValueType.MEXIF_VALUE_BYTE   :		buf.append( "byte" );		break;
		case EnumValueType.MEXIF_VALUE_UINT16 :		buf.append( "uint16" );	break;
		case EnumValueType.MEXIF_VALUE_UINT32 :		buf.append( "uint32" );	break;
		case EnumValueType.MEXIF_VALUE_SINT8  :		buf.append( "char" );		break;
		case EnumValueType.MEXIF_VALUE_SINT16 :		buf.append( "int16" );	break;
		case EnumValueType.MEXIF_VALUE_SINT32 :		buf.append( "int32" );	break;
		case EnumValueType.MEXIF_VALUE_RATIONAL :		buf.append( "float" );	break;
		case EnumValueType.MEXIF_VALUE_SRATIONAL :	buf.append( "float" );	break;
		default : buf.append("Unknown-"); buf.append(tag.mValueType); break;
		}

		if( tag.mValueCount > 1 ) {
			buf.append("[");
			buf.append(tag.mValueCount);
			buf.append("]");
		}
		return buf.toString();
	}
	
	static String formatTagValue(ExifTag tag) {
//		specific tag formatting.
		if ( tag.mIFD.mSectionFormat == EnumExifSection.MEXIF_SECTION_0 ) {
			if ( tag.mTagID == ExifMetaTagDB_EXIF.MEXIF_TAGID_EXIF_DateTime ) {
				String str = formatTagValue_DateTime(tag);
				if( str != null )
					return str;
			}
		}
		else if ( tag.mIFD.mSectionFormat == EnumExifSection.MEXIF_SECTION_EXIF ) {
			if ( tag.mTagID == ExifMetaTagDB_EXIF.MEXIF_TAGID_EXIF_DateTimeOriginal || tag.mTagID == ExifMetaTagDB_EXIF.MEXIF_TAGID_EXIF_DateTimeDigitized ) {
				String str = formatTagValue_DateTime(tag);
				if( str != null )
					return str;
			}
		}

		String str = null;
		//	general formatting.
		switch( tag.mValueType )
		{
		case MEXIF_VALUE_BYTE   :
		case MEXIF_VALUE_UINT16 :
		case MEXIF_VALUE_UINT32 :
		case MEXIF_VALUE_SINT8  :
		case MEXIF_VALUE_SINT16 :
		case MEXIF_VALUE_SINT32 :
		case MEXIF_VALUE_RATIONAL :
		case MEXIF_VALUE_SRATIONAL :
			str = formatTagNumber( tag, 4 );
			break;

		case MEXIF_VALUE_UNDEFINED :
			str = formatTagNumber( tag, 16 );
			break;

		case MEXIF_VALUE_ASCII :
			//	change to unicode.
			StringBuffer buf = new StringBuffer();
			buf.append('"');
			for(int i=tag.mValuePointerOffset; ; i++) {
				int val = tag.mExif.mData[i]; 
				if( val == 0 )
					break;
				switch(val) {
				case 0		: buf.append("\\0");
				case '\r'	: buf.append("\\r");
				case '\n'	: buf.append("\\n");
				case '\t'	: buf.append("\\t");
				case '\\'	: buf.append("\\\\");
				case '\"'	: buf.append("\\\"");
				default		: buf.append( (char) val );
				}
			}
			buf.append( '"' );
			str = buf.toString();
			break;
		}
		return str;
	}

	static String formatTagValue_DateTime( ExifTag tag) {
		ExifTime time = tagValueTime(tag);
		if( time != null ) {
			StringBuffer buf = new StringBuffer();
			buf.append(time.year);
			buf.append('.');
			buf.append(time.month);
			buf.append('.');
			buf.append(time.day);
			buf.append(' ');
			buf.append(time.hour);
			buf.append(':');
			buf.append(time.minute);
			buf.append(':');
			buf.append(time.second);
			return buf.toString();
		}
		else
			return null;
			
	}

	static String formatTagNumber( ExifTag tag, int maxCount) {
		if( tag.mValueCount < maxCount )
			maxCount = tag.mValueCount;
		
		StringBuffer buf = new StringBuffer();
		if( maxCount > 1 )
			buf.append("{ ");
		
		for(int i=0; i<maxCount; i++) {
			int val;
			switch( tag.mValueType )
			{
			case MEXIF_VALUE_BYTE   :
			case MEXIF_VALUE_UINT16 :
			case MEXIF_VALUE_UINT32 :
				val = (int) uintTagValueIndex( tag, i );
				if ( ( val & 0xff000000 ) != 0) {
					buf.append("0x");
					buf.append(Integer.toHexString(val));
					buf.append(" (");
					buf.append(val);
					buf.append(')');
				}
				else
					buf.append(val);
				break;

			case MEXIF_VALUE_SINT8  :
			case MEXIF_VALUE_SINT16 :
			case MEXIF_VALUE_SINT32 :
				buf.append(intTagValueIndex( tag, i ));
				break;

			case MEXIF_VALUE_RATIONAL :
			case MEXIF_VALUE_SRATIONAL :
				buf.append( stringDoubleTagValueIndex( tag, i ) );
				break;

			case MEXIF_VALUE_UNDEFINED :
				buf.append(tag.mValuePointerOffset);
				break;
			}
		}
		return buf.toString();
	}
		
	// @TODO: Tag Value Time
	static ExifTime tagValueTime(ExifTag tag) {
		// Check if the tag is a time type or not.
		if( tag.mValueType == MEXIF_VALUE_ASCII && tag.mValueCount >= 20 ) {
			int p = tag.mValuePointerOffset;
			ExifTime t = new ExifTime();
			
			// Should know how many to skip
			t.year = (short) atoui(tag.mExif.mData, p);
			p = findNonDigit(tag.mExif.mData, p);
			if( p == -1 )
				return null;
			
			++p;
			t.month = (short) atoui(tag.mExif.mData, p);
			p = findNonDigit(tag.mExif.mData, p);
			if( p == -1 )
				return null;

			++p;
			t.day = (short) atoui(tag.mExif.mData, p);
			p = findNonDigit(tag.mExif.mData, p);
			if( p == -1 )
				return null;

			++p;
			t.hour = (short) atoui(tag.mExif.mData, p);
			p = findNonDigit(tag.mExif.mData, p);
			if( p == -1 )
				return null;

			++p;
			t.minute = (short) atoui(tag.mExif.mData, p);
			p = findNonDigit(tag.mExif.mData, p);
			if( p == -1 )
				return null;
			
			++p;
			t.second = (short) atoui(tag.mExif.mData, p);
			return t;
		}
		return null;
	}
	
	static int atoui(byte[] data, int offset) {
		int ui = 0;
		for(int i=offset; i<data.length; i++) {
			if( data[i] >= '0' && data[i] <= '9' ) {
				ui = ui*10 + data[i] - '0';
			}
			else
				break;
		}
		return ui;
	}
	
	static int findNonDigit(byte[] data, int offset) {
		for(int i=offset; i<data.length; i++) {
			if( data[i] >= '0' && data[i] <= '9' )
				continue;
			else
				return i;
		}
		return data.length;
	}
//	mexif_return_t MEXIF_API mexif_tag_value_time( mexif_tag_t* tag, mexif_time_t* t ) 
//	{
//		//	check if tag is a time type.
//		if ( tag->value_type == MEXIF_VALUE_ASCII && tag->value_count >= 20 )
//		{
//			const unsigned char* p = tag->value_pointer;
//			memset( t, 0, sizeof(mexif_time_t) );
//
//			//	should know how many to skip
//			t->year = mexif_atoui( p );
//			p = mexif_find_non_digit( p );
//			if ( *p == 0 )
//				return MEXIF_RET_ERROR;
//
//			++p;
//			t->month = mexif_atoui( p );
//			p = mexif_find_non_digit( p );
//			if ( *p == 0 )
//				return MEXIF_RET_ERROR;
//
//			++p;
//			t->date = mexif_atoui( p );
//			p = mexif_find_non_digit( p );
//			if ( *p == 0 )
//				return MEXIF_RET_ERROR;
//
//			++p;
//			t->hour = mexif_atoui( p );
//			p = mexif_find_non_digit( p );
//			if ( *p == 0 )
//				return MEXIF_RET_ERROR;
//
//			++p;
//			t->minute = mexif_atoui( p );
//			p = mexif_find_non_digit( p );
//			if ( *p == 0 )
//				return MEXIF_RET_ERROR;
//
//			++p;
//			t->second = mexif_atoui( p );
//			return MEXIF_RET_OK;
//		}
//		else
//			return MEXIF_RET_ERROR;
//	}

}
